﻿/*
 * Copyright (c) 2010-2011, Omer
 * All rights reserved.
 * The code is under the BSD license.
 * http://minecraftbackup.codeplex.com/license
 */
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using LibNbt;
using LibNbt.Tags;
using System.Collections.Generic;

namespace MinecraftBackupAssistant
{
    public partial class MainForm : Form
    {
        private Thread minecraftCheckForExitThread;

        private ToolTip tip;
        
        private string minecraftFolder;

        private BindingList<World> worlds;

        public MainForm()
        {
            InitializeComponent();

			if (!getMinecraftFolder())
			{
				MessageBox.Show(".minecraft saves folder does not exists!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

				setBackupState(false);

				settingsButton.Enabled = true;
				openBackupButton.Enabled = true;
			}
			else
			{
				this.loadSettings();
				this.loadWorldList();
			}
        }

		bool getMinecraftFolder() //returns whether the .minecraft folder exists
		{
			minecraftFolder = Properties.Settings.Default.MinecraftPortablePath != "" ? Properties.Settings.Default.MinecraftPortablePath
				: Utility.CombinePath(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".minecraft", "saves");

			return Directory.Exists(minecraftFolder);
		}

        void loadWorldList() 
		{
			worldComboBox.SelectedIndexChanged -= worldComboBox_SelectedIndexChanged; //to avoid the SelectedIndexChanged event from firing when populating the list

            DirectoryInfo[] directories = new DirectoryInfo(minecraftFolder).GetDirectories();

            NbtFile file;
            worlds = new BindingList<World>();
            List<World> temp = new List<World>();

            string text = "";
            DateTime time = new DateTime();

            for (int i = 0; i < directories.Length; i++)
            {
                string levelName;
                if (File.Exists(Path.Combine(directories[i].FullName, "level.dat")))
                {
                    file = new NbtFile(Path.Combine(directories[i].FullName, "level.dat"), true);//get lastplayed data from the world
                    file.LoadFile();

                    NbtLong lastPlayed = (NbtLong)((NbtCompound)file.RootTag.Tags[0]).Tags.Find(new Predicate<NbtTag>(tag => tag.Name == "LastPlayed"));
                    levelName = ((NbtString)((NbtCompound)file.RootTag.Tags[0]).Tags.Find(new Predicate<NbtTag>(tag => tag.Name == "LevelName"))).Value;

                    time = new DateTime(1970, 1, 1, 0, 0, 0).AddMilliseconds(((NbtLong)lastPlayed).Value);
                    text = ", last played: " + time.ToLocalTime().ToString();
                }
                else
                {
                    //levelName = directories[i].Name;
                    //text = ", last played: " + directories[i].LastWriteTime.ToLocalTime().ToString();

					//the folder is not a save - skip
					continue;
                }
                temp.Add(new World(levelName, levelName + " (" + directories[i].Name + ")" + text, directories[i].FullName, time));
            }

			if (temp.Count == 0) //no saves in the folder
			{
				MessageBox.Show("No saves detected in the minecraft saves folder.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

				setBackupState(false);

				settingsButton.Enabled = true;
				openBackupButton.Enabled = true;

				return;
			}

            temp.Sort(new Comparison<World>((a, b) => DateTime.Compare(b.LastPlayed, a.LastPlayed)));

            for (int i = 0; i < temp.Count; i++)
                worlds.Add(temp[i]);

            worldComboBox.DisplayMember = "DisplayName";
            worldComboBox.ValueMember = "Path";
            worldComboBox.DataSource = worlds;

            Utility.AdjustComboBoxWidth(worldComboBox);

            renameBackupTextBox.Text = new DirectoryInfo((string)worldComboBox.SelectedValue).Name;

			worldComboBox.SelectedIndexChanged += worldComboBox_SelectedIndexChanged;
        }

        private void loadSettings()
        {
			string worldToBackup = Properties.Settings.Default.WorldToBackup;
			if (worldToBackup != String.Empty)
				for (int i = 0; i < worldComboBox.Items.Count; i++)
				{
					if ((worldComboBox.Items[i] as World).WorldName == worldToBackup)
					{
						worldComboBox.SelectedIndex = i;
						break;
					}
				}
						

            /*if (worldComboBox.Items.Count == 0)
            {
                MessageBox.Show("There are no worlds to backup.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

                backupButton.Enabled = false;
                worldComboBox.Enabled = false;
                deleteButton.Enabled = false;
            }*/
            

            if (!Properties.Settings.Default.SettingsEdited)
            {
                setBackupState(false);
                settingsButton.Enabled = true;
            }
            else
                setBackupState(true);
        }
        private void setBackupState(bool state)
        {
            deleteButton.Enabled = state;
            backupButton.Enabled = state;
            worldComboBox.Enabled = state;
            restoreButton.Enabled = state;
            descriptionTextBox.Enabled = state;
            startGameButton.Enabled = state;
            openSaveButton.Enabled = state;
            openBackupButton.Enabled = state;
            settingsButton.Enabled = state;
            label1.Enabled = state;
            label2.Enabled = state;
            renameBackupTextBox.Enabled = state;

            menuStrip1.Enabled = state;

            this.ActiveControl = null;
        }

        private void startGameButton_Click(object sender, EventArgs e)
        {
            if (Properties.Settings.Default.EXEPath == "" && !File.Exists(Properties.Settings.Default.EXEPath))
            {
                MessageBox.Show("The minecraft.exe path that you have selected does not exists.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            minecraftCheckForExitThread = new Thread(() =>
            {
                try
                {
                    DateTime startTime = DateTime.Now;

                    Process minecraftStarter = new Process();
                    minecraftStarter.StartInfo = new ProcessStartInfo(Properties.Settings.Default.EXEPath);
                    minecraftStarter.Start();
                    minecraftStarter.WaitForExit();

                    Process[] javaProcesses = Process.GetProcessesByName("javaw", minecraftStarter.MachineName);
                    Process minecraft = null;
                    foreach (Process process in javaProcesses)
                    {
                        if (process.StartTime > startTime)
                        {
                            minecraft = process;
                            break;
                        }
                    }
                    if (minecraft != null)
                    {
                        minecraft.WaitForExit();
                        this.Invoke(new Action(() => backup(null)));
                    }
                }
                catch (System.ComponentModel.Win32Exception ex)
                {
                    if (ex.NativeErrorCode != 1223)
                    {
                        Utility.HandleError(ex);
                    }
                }
            });
            minecraftCheckForExitThread.Start();

        }
        #region Backup

        private void backupButton_Click(object sender, EventArgs e)
        {
            backup(null);
        }

        private void backup(Action endAction)
        {
            var settings = Properties.Settings.Default;
            string backupName = renameBackupTextBox.Text;
            string folder = worldComboBox.SelectedValue.ToString();

            if (!Directory.Exists(settings.BackupPath) || settings.BackupPath == "")
            {
                MessageBox.Show("The backup path you have selected does not exists. Please select another one before trying to backup.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (settings.ShouldMap && !File.Exists(settings.MapperPath))
            {
                MessageBox.Show("The Mapper path you have selected does not exists. Please select another one before trying to backup with a Mapper image attached.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (renameBackupTextBox.Text == "")
            {
                DialogResult result = MessageBox.Show("You did not choose a name for the backup. Do you want to name it the same as the world?", "Error", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                if (result == DialogResult.Yes)
                    backupName = new DirectoryInfo(folder).Name;
                else
                    return;
            }

            backupProgressBar.Visible = true;
            Height += 30;
            foreach (Control control in Controls)
                if (control.Top > backupProgressBar.Top) control.Top += 30;

            this.setBackupState(false);
            loadingPictureBox.Visible = true;
            this.Refresh();

            Action<string> action = new Action<string>((str) =>
                {
                    if (endAction != null)
                        endAction();

                    finishBackup(str);
                });

            Action<Exception> onMinorError = new Action<Exception>((e) => MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error));
            Backup backup = new Backup(new BackupInformation(settings.ShouldZip, folder, settings.ShouldMap,
                                                             settings.BackupPath, backupName, descriptionTextBox.Text, settings.SelectedDateTimeFormat,
                                                             settings.MapperPath, settings.MapperSettings, settings.NumberBackupsToKeep),
                                        updateProgress, this.Invoke, action, Utility.HandleError, onMinorError, new Action<string>((str) =>
                                                                                                                    this.Invoke(new Action(() =>
                                                                                                                    {
                                                                                                                        toolStripStatusLabel1.Text = str;
                                                                                                                        statusStrip1.Update();
                                                                                                                    }))), 
                                                                                                            true);



            backup.BackupWorld();
        }

        private void finishBackup(string endText)
        {
            //delete any DotNetZip temporary files
            foreach (var file in new DirectoryInfo(Properties.Settings.Default.BackupPath).GetFiles(@"DotNetZip*.tmp"))
                file.Delete();

            backupProgressBar.Visible = false;
            backupProgressBar.Value = 0;
            loadingPictureBox.Visible = false;
            setBackupState(true);

            Height -= 30;
            foreach (Control control in Controls)
                if (control.Top > backupProgressBar.Top) control.Top -= 30;

            if (endText != "")
            {
                toolStripStatusLabel1.Text = endText;
                System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer() { Interval = 5000 };
                timer.Tick += ((sender, e) =>
                {
                    this.Invoke(new Action(() =>
                    {
                        if (toolStripStatusLabel1.Text == endText)
                            toolStripStatusLabel1.Text = "";
                        timer.Stop();
                    }));
                });
                timer.Start();
            }
        }
        private void updateProgress(int maximum, int value)
        {
            backupProgressBar.Maximum = maximum;
            backupProgressBar.Value = value;
        }

        #endregion Backup

        private void worldComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
			renameBackupTextBox.Text = (worldComboBox.SelectedItem as World).WorldName;
			Properties.Settings.Default.WorldToBackup = (worldComboBox.SelectedItem as World).WorldName;
            Properties.Settings.Default.Save();
        }


        private void openSaveButton_Click(object sender, EventArgs e)
        {
            Process.Start(minecraftFolder);
        }

        private void openBackupButton_Click(object sender, EventArgs e)
        {
            if (Properties.Settings.Default.BackupPath == "")
            {
                MessageBox.Show("You have not selected a backup path.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            if (!Directory.Exists(Properties.Settings.Default.BackupPath))
            {
                MessageBox.Show("The backup path you have selected does not exist.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            Process.Start(Properties.Settings.Default.BackupPath);
        }

        private void deleteButton_Click(object sender, EventArgs e)
        {
            if (worldComboBox.Items.Count == 1)
            {
                MessageBox.Show("You must have at least one world.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            string world = new DirectoryInfo((string)worldComboBox.SelectedValue).Name;
            DialogResult result = MessageBox.Show("You are about to delete " + world + ". Do you want to create a backup first?", "Delete Save", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information);
            Action action = new Action(() =>
                {
                    Utility.DeleteDirectory((string)worldComboBox.SelectedValue);
                    MessageBox.Show(world + " has been deleted.", "Save Deleted", MessageBoxButtons.OK, MessageBoxIcon.Information);

                    //worldComboBox.Items.Remove(worldComboBox.SelectedItem);
                    worlds.Remove((World)worldComboBox.SelectedItem);
                    worldComboBox.DataSource = worlds;
                    if (worldComboBox.Items.Count > 0) worldComboBox.SelectedIndex = 0;
                });
            if (result == DialogResult.Yes)
                this.backup(action);
            else if (result == DialogResult.No)
                action();

        }

        private void swapSavesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            openNewForm<Forms.SavesUtilityForm>(new Action<Forms.SavesUtilityForm>((f) => f.UpdateForm = new Action(() => { loadWorldList(); loadSettings(); })));
        }

        private void restoreButton_Click(object sender, EventArgs e)
        {
            openNewForm<Forms.RestoreForm>(new Action<Forms.RestoreForm>((f) => f.UpdateForm = new Action(() => { loadWorldList(); loadSettings(); })));
        }

        private void informationToolStripMenuItem_Click(object sender, EventArgs e)
        {
            openNewForm<Forms.HelpForm>(false, true);
        }

        private void openNewForm<T>() where T : Form, new()
        {
            openNewForm<T>(true, false);
        }
        private void openNewForm<T>(bool hideForm, bool dialog) where T : Form, new()
        {
            openNewForm<T>(null, hideForm, dialog);
        }
        private void openNewForm<T>(Action<T> specialAction) where T : Form, new()
        {
            openNewForm<T>(specialAction, true, false);
        }
        private void openNewForm<T>(Action<T> specialAction, bool hideForm, bool dialog) where T : Form, new()
        {
            T form = new T();
            if (hideForm)
            {
                Hide();

                form.FormClosed += ((sender, e) =>
                {
                    Show();
                    Focus();
                });
            }

            form.Icon = this.Icon;

            if (specialAction != null) 
                specialAction(form);

            if (!dialog)
                form.Show();
            else
                form.ShowDialog();
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Minecraft Backup Assistant\nVersion " + Utility.CurrentVersion + "\nCreated by omer681", "About", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void getUpdatesAndReportBugsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Process.Start("http://minecraftbackup.codeplex.com/discussions");
        }


        private void customNameTextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            string letters = "\\ / . : * ? \"< > |";
            
            for (int i = 0; i< letters.Length; i++)
            {
                if (e.KeyChar != ' ' && e.KeyChar == letters[i])
                {
                    if (tip == null)
                        tip = new ToolTip();
                    tip.Show("The name cannot contain the letters " + letters, this, descriptionTextBox.Location.X, descriptionTextBox.Location.Y + 45, 5000);

                    e.Handled = true;
                    break;
                }
            }
        }

        private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            toolStripStatusLabel1.Text = "Checking for an update...";
            loadingPictureBox.Visible = true;
            setBackupState(false);
            Thread thread = new Thread(() =>
                {
                    try
                    {
                        UpdateChecker.Update(true);
                    }
                    catch (System.Net.WebException)
                    {
                        MessageBox.Show("Can't connect to the internet", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                    catch (Exception ex)
                    {
                        this.Invoke(new Action<Exception>((exc) => Utility.HandleError(exc)), ex);
                    }
                    this.Invoke(new Action<bool>(setBackupState), true);
                    toolStripStatusLabel1.Text = "";
                    this.Invoke(new Action(() => loadingPictureBox.Visible = false));
                });
            thread.Start();
        }

        private void settingsButton_Click(object sender, EventArgs e)
        {
            openNewForm<Forms.SettingsForm>(new Action<Forms.SettingsForm>((form) => form.SettingsUpdated += new Action(() =>
                {
					if (this.getMinecraftFolder())
					{
						this.loadSettings();
						this.loadWorldList();
					}
					else
					{
						MessageBox.Show(".minecraft saves folder does not exists!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

						setBackupState(false);

						if (worlds != null) worlds.Clear();
						renameBackupTextBox.Clear();
						descriptionTextBox.Clear();

						settingsButton.Enabled = true;
						openBackupButton.Enabled = true;
					}
                })), false, true);            
        }
    }


    class World
    {
        /*private string displayName;
        private string path;
        public DateTime LastPlayed;*/

		public string WorldName { get; set; }
		public string DisplayName { get; set; }
		public string Path { get; set; }
		public DateTime LastPlayed { get; set; }

        public World(string worldName, string displayName, string path, DateTime lastPlayed = new DateTime())
        {
			WorldName = worldName;
			DisplayName = displayName;
			Path = path;
            LastPlayed = lastPlayed;
        }

	/*public string DisplayName
        {
            get { return displayName; }
        }
        public string Path
        {
            get { return path; }
        }*/

    }
}
